package p02;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;

/**
 * 相比于BeanFactory的简陋功能，ApplicationContext则要强大许多
 * ApplicationContext主动做好了很多工作，比如加入并运行后处理器，初始化好所有单例bean...
 * 介绍ApplicationContext接口实现类中经典的四个。
 *
 * @author Chen weishuai
 * @since 2025/7/17
 */
@Slf4j
public class ApplicantContextDemo {

    public static void main(String[] args) {
        ApplicantContextDemo imp = new ApplicantContextDemo();
        /**
         * 这两个实现类内部是基于DefaultListableBeanFactory创建的
         * 通过XmlBeanDefinitionReader读取xml并转化为bean定义对象加入容器，再根据DefaultListableBeanFactory创建出bean
         *      XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
         *      reader.loadBeanDefinitions("xml文件对象")
         * 这两个实现类并不会默认添加后处理器，必须在xml文件中手动添加<context:annotation-config/>
         */
        imp.classPathXmlApplicationContext();
        imp.fileSystemXmlApplicationContext();
        /**
         * 该实现类添加了5个后处理器，所以该实现类天然具有解析一些注解的能力
         * org.springframework.context.annotation.internalConfigurationAnnotationProcessor
         * org.springframework.context.annotation.internalAutowiredAnnotationProcessor
         * org.springframework.context.annotation.internalCommonAnnotationProcessor
         * org.springframework.context.event.internalEventListenerProcessor
         * org.springframework.context.event.internalEventListenerFactory
         */
        imp.annotationConfigApplicationContext();
        /**
         * 这种实现类借助了内嵌tomcat容器作为服务器，tomcat本质就是容器内的一个bean
         */

        imp.annotationConfigServletWebServerApplicationContext();

    }

    public void annotationConfigServletWebServerApplicationContext() {
        /**
         * web环境，需要更多的组件，所以需要加入更多的bean，比如
         *  1.ServletWebServerFactory(产生tomcat服务器)
         *  2.DispatcherServlet(前处理器)
         *  3.DispatcherServletRegistrationBean(将前处理注册为tomcat服务器的一个servlet)
         *  除了以上三个必备的组件，还可以加入一些诸如Controller控制器bean
         *
         *  org.springframework.context.annotation.internalConfigurationAnnotationProcessor
         * org.springframework.context.annotation.internalAutowiredAnnotationProcessor
         * org.springframework.context.annotation.internalCommonAnnotationProcessor
         * org.springframework.context.event.internalEventListenerProcessor
         * org.springframework.context.event.internalEventListenerFactory
         * 相较于AnnotationConfigApplicationContext，该实现类还需要手动加入下面的组件 才能实现Web功能
         *servletWebServerFactory
         * dispatcherServlet
         * registrationBean
         *
         */
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        log.info("启动成功");
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }


    /**
     * 经典容器：基于配置类作为容器
     * 该实现类自动添加了5种后处理器
     */
    public void annotationConfigApplicationContext() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        // Config类上的@Configuration注解没有影响Config/bean
        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
    }


    /**
     * 经典容器：从磁盘路径下读取xml文件作为容器，与classPathXmlApplicationContext别无二致
     * 相对路径写法可以从src目录开始
     */
    public void fileSystemXmlApplicationContext() {
        FileSystemXmlApplicationContext context =
                new FileSystemXmlApplicationContext("src/main/resources/p02/TestPathXml2.xml");
        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
    }

    /**
     * 经典容器：从类路径下读取xml文件作为容器
     */
    public void classPathXmlApplicationContext() {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("classpath:p02/TestPathXml2.xml");
        // 上面的代码与下面四行代码等价
//        DefaultListableBeanFactory context = new DefaultListableBeanFactory();
//        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
//        reader.loadBeanDefinitions("classpath:p02/TestPathXml2.xml");
//        context.preInstantiateSingletons();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
    }

    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean1 {

        private Bean2 bean2;

        public Bean1() {
            log.info("Bean1 init");
        }

        public void setBean(Bean2 bean2) {
            this.bean2 = bean2;
        }
    }

    static class Bean2 {
        public Bean2() {
            log.info("Bean2 init");
        }
    }

    @Configuration
    static class WebConfig {
        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            // 创建服务器bean tomcat也只是一个bean
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public DispatcherServlet dispatcherServlet() {
            // 创建DispatcherServlet
            return new DispatcherServlet();
        }

        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
            // 创建DispatcherServletRegistrationBean
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }

        @Bean("/hello")
        public Controller controller1() {
            return (request, response) -> {
                response.getWriter().println("hello");
                return null;
            };
        }
    }


}
